#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
Parse pack details and mange card/tile sets.
"""
__docformat__ = 'restructuredtext en'

import logging

LOGGER = logging.getLogger('PySolFC.Packlib.Card')

from ..utillib.misc import Cast, Point, ImagePair
from ..viewlib import canvas


class AbstractCard(object):
    """
     A card/tile image item for canvas.

     *note*
       Doesn't directly record which pile holds item. Only pile tracks this,
       though pile may tag the card for its group. Saves double update with
       potential for inconsistency.
    """
    __fields__ = ('_face', '_item', '_image', '_hide')
    _id = 0
    _cast = None

    def __new__(cls, *args, **kwargs):
        """
        Create new class to avoid a singleton.
        """
        card_ = super(AbstractCard, cls).__new__(cls, *args, **kwargs)
        return card_

    def __init__(self, canvas, index, point, cast, image):
        """
        Init card/tile on canvas.


        Set given index, cast and point then call update method with given
        canvas and image to place with its back visible.

        .. \__init__(canvas:Canvas, index:int, coord:Point(x:int, y:int),
          cast:Cast(deck:int, suit:int, rank:int, color:int, force:int),
          imagepair:ImagePair(back, front)) -> None

        *Parameters:*
          ``canvas``: {Canvas}

          ``index``: {number}

          ``coord``: Point(x:number, y:number)

          ``cast``:  Cast(deck:number, suit:number, rank:number, color:number,
          force:number)

          ``imagepair``: ImagePair(back:Image, front:Image)

          *Require:*
            methods:
              ``.update``.

            variables:
              ``._id`` - index in pack sequence of cards/tiles created.

              ``._cast`` - attributes of card/tile used for sequence comparison.

              ``._face`` - current image pair side visible.

              ``._item`` - current image item on canvas.

              ``._image`` - current image pair for item.

              ``._hide`` - card/tile hidden on pile.
        """
        super(AbstractCard, self).__setattr__('_id', index)
        super(AbstractCard, self).__setattr__('_cast', Cast(*cast))
        self._face = False
        self._item = self._image = self._hide = None
        self.update(canvas, Point(*point), image.face, image.back)

    def __getstate__(self):
        """
        WIP: Card state getter.

        Store class to Pickle.
        """
        return {'id': self._id, 'cast': self._cast, 'face_up': self._face,
                'coord': self.coord}

    def __setstate__(self, data):
        """
        WIP: Card state setter.

        Restore class from Pickle.
        """
        super(AbstractCard, self).__setattr__('_id', data['id'])
        super(AbstractCard, self).__setattr__('_cast', Cast(*data['cast']))
        self._face = data['face_up']
        self.move_to(*data['coord'])

    def __setattr__(self, name, value):
        """
        Set only attributes from fields value.
        """
        if name in self.__fields__ or name == '__dict__':
            super(AbstractCard, self).__setattr__(name, value)
        else:
            raise AttributeError('Attributes are frozen')

    def __hash__(self):
        """
        Return unique hash based cast attribute.

        .. hash(Card) -> str

        *Requires*:
          variables:
            ``._cast``
        """
        return hash(self._cast)

    def __getitem__(self, name):
        """
        Return card/tile cast attribute.

        .. Card[name] -> int
        """
        if name in self._cast._asdict():
            return self._cast._asdict()[name]
        raise KeyError

    def __repr__(self):
        """
        Return class representation.

        .. \__repr__() -> str
        """
        return '%s <%s: %s>' % (self.__class__.__name__, self._id, hash(self))

    @property
    def coord(self):
        """
        Return card/tile Point coord.

        Gets internal image item coord attribute for its location on the canvas.

        .. coord -> Point(int, int)

        *Requires*:
          methods:
            ``Point``.
          variables:
            ``._item.coord``.
        """
        return Point(*self._item.coord)

    @property
    def x(self):
        """
        Return card/tile x coord.

        Gets image item coord.x attribute for its x-axis location on the canvas.

        .. x -> int

        *Requires*:
          variables:
            ``.coord``.
        """
        return self.coord.x

    @property
    def y(self):
        """
        Return card/tile y coord.

        Gets image item coord.y attribute for its y-axis location on the canvas.

        .. y -> int

        *Requires*:
          variables:
            ``.coord``.
        """
        return self.coord.y

    @property
    def hidden(self):
        """
        Return card/tile hidden.

        .. hidden -> bool

        *Requires*:
          variables:
            ``._hide``
        """
        return self._hide is not None

    @property
    def face_up(self):
        """
        Return card/tile face up.

        .. face_up -> bool

        *Requires*:
          variables:
            ``._face``
        """
        return self._face

    @property
    def _pil_image(self):
        """
        Return card\tile Image.

        .. \_pil_image -> Image
        """
        image = self._image[int(self._face)]
        if hasattr(image, '_pil_image'):
            return image._pil_image

    def move_to(self, xcoord, ycoord):
        """
        Move card/tile to absolute position.

        Call item move to method with given values.

        .. move_to(x:int, y:int) -> None

        *Parameters:*
          ``xcoord``: {number}
            Coordinate point x-axis to place card/tile.
          ``ycoord``: {number}
            Coordinate point y-axis to place card/tile.

        *Requires*:
          methods:
            ``._item.move_to``.
        """
        LOGGER.info('Absolute Move: %r (%s, %s)', self, xcoord, ycoord)
        self._item.move_to(xcoord, ycoord)

    def move(self, xdelta, ydelta):
        """
        Move card/tile to relative position.

        Call item move method with given values.

        .. move(xdelta:int, ydelta:int) -> None

        *Parameters:*
          ``xdelta``: {number}
            x-axis width to move card/tile.
          ``ycoord``: {number}
            y-axis height to move card/tile.

        *Requires*:
          methods:
            ``._item.move``.
        """
        LOGGER.info('Relative Move: %r (%s, %s)', self, xdelta, ydelta)
        self._item.move(xdelta, ydelta)

    def raise_item(self, unhide=True):
        """
        Raise canvas item image.

        When given unhide is true, call unhide method. Call item raise method.

        .. raise_item([unhide:bool=True]) -> None

        *Parameters:*
          ``unhide``: {bool} True
            When True, unhide card before raise.

        *Requires*:
          methods:
            ``.unhide``; ``._item.raise_item``.
        """
        if unhide:
            self.unhide()
        LOGGER.info('Raise: %r (%s, %s)', self, self.x, self.y)
        self._item.raise_item()

    def lower_item(self):
        """
        Lower canvas item image.

        When given unhide is true, call unhide method. Call item raise method.

        .. lower_item() -> None

        *Requires*:
          methods:
            ``._item.lower_item``.
        """
        LOGGER.info('Lower: %r (%s, %s)', self, self.x, self.y)
        self._item.lower_item()

    def hide(self, pile):
        """
        Hide canvas item image.

        Call item hide method and set hide with given pile. Return actions
        occurred.

        .. hide(pile:Pile) -> bool

        *Parameters:*
          ``pile``: {Pile}
            Set pile hidden on.

        *Requires*:
          methods:
            ``._item._hide``.
          variables:
            ``.hidden``; ``._hide``.
        """
        if pile is self.hidden:
            return False
        LOGGER.info('Unhide: %r (%s, %s)', self, self.x, self.y)
        self._item.hide()
        self._hide = pile
        return True

    def unhide(self):
        """
        Unhide canvas item image.

        Call item show method and unset hide. Return actions occurred.

        .. unhide()  -> bool

        *Requires*:
          methods:
            ``._item._show``.
          variables:
            ``.hidden``; ``._hide``.
        """
        if not self.hidden:
            return False
        LOGGER.info('Unhide: %s (%s, %s)', self, self.x, self.y)
        self._item.show()
        self._hide = None
        return True

    def _set_side(self, unhide=True):
        """
        Set card/tile image side from pair.

        Stub for subclass.

        .. \_set_side([unhide:bool=True])

        *Parameters:*
          ``unhide``: {bool} True
            When True, unhide card before set face image.
        """
        raise NotImplementedError

    def show_face(self, unhide=True):
        """
        Turn card/tile face up.

        Call set side method to select face image from pair.

        ..show_face([unhide:bool=True]) -> None

        *Parameters:*
          ``unhide``: {bool} True
            When True, unhide card before set face image.

        *Requires*:
          methods:
            ``._set_side``.
          variables:
            ``._face``.
        """
        if not self._face:
            self._face = True
            self._set_side(unhide)

    def show_back(self, unhide=True):
        """
        Turn card/tile face down.

        Call set side method to select back image from pair.

        .. show_back([unhide:bool=True]) -> None

        *Parameters:*
          ``unhide``: {bool} True
            When True, unhide card before set back image.

        *Requires*:
          methods:
            ``._set_side``.
          variables:
            ``._face``.
        """
        if self._face:
            self._face = False
            self._set_side(unhide)

    def update_back(self, back):
        """
        Update card/tile back image from pair.

        Stub for subclass.

        .. update_back(back:Image) -> None

        *Parameters:*
          ``back``: {Image}
            Image to set as back for ImagePair.
        """
        raise NotImplementedError

    def update(self, canvas, coord, face, back):
        """
        Update card/tile image pair.

        Stub for subclass.

        .. update(canvas:Canvas, coord:Point, face:Image, back:Image) -> None

        *Parameters:*
          ``Canvas``: {Canvas}
            Canvas widget.
          ``coord``: {Point(x:int, y:int)}
            Point to place on canvas.
          ``front``: {Image}
            Image to set as front for ImagePair.
          ``back``: {Image}
            Image to set as back for ImagePair.
        """
        raise NotImplementedError


class _TwoImageCard(AbstractCard):
    """
    Double canvas ImageItem. Show visible side and hide other within canvas
    GroupItem.
    """

    def _set_side(self, unhide=True):
        """
        Set card/tile image side from pair.

        Call show method on current side and hide method on other of ImagePair.

        .. \_set_side([unhide:bool=True])

        *Parameters:*
          ``unhide``: {bool} True

        *Requires*:
          methods:
            ``._item.raise_item``; ``._item.show``; ``._item.hide``.
          variables:
            ``._image``; ``._face``.
        """
        self._image[int(self._face)].show()
        self._image[int(not self._face)].hide()
        self.raise_item(unhide)

    def update_back(self, back):
        """
        Update card/tile back image from pair.

        Set back image of ImagePair with given back.

        .. update_back(back:Image) -> None

        *Parameters:*
          ``back``: {Image}
            Image to set as back for ImagePair.

        *Requires*:
          methods:
            ``._item.config``.
          variables:
            ``._image``.
        """
        self._image.back.config(image=back)

    def update(self, parent, coord, face, back):
        """
        Update card/tile image pair.

        Set ImagePair as canvas ImageItem at given coord and tag with item
        index. Call set side method to show current side.

        .. update(parent:Canvas, coord:Point, face:Image, back:Image) -> None

        *Parameters:*
          ``parent``: {Canvas}
            Canvas widget.
          ``coord``: {Point(x:int, y:int)}
            Point to place on canvas.
          ``front``: {Image}
            Image to set as front for ImagePair.
          ``back``: {Image}
            Image to set as back for ImagePair.

        *Requires*:
          methods:
            ``canvas.ImageItem``; ``canvas.GroupItem``; ``._item.addtag``;
            ``_set_side``.
          variables:
            ``._item.``; ``._image``.
        """
        self._item = canvas.GroupItem(parent)
        self._image = ImagePair(back=canvas.ImageItem(parent, coord, image=back),
                                face=canvas.ImageItem(parent, coord, image=face))
        self._image.face.addtag(self._item)
        self._image.back.addtag(self._item)
        self._set_side()


class _OneImageCard(AbstractCard):
    """
    Single canvas ImageItem. Replace visible side with canvas image.
    """

    def _set_side(self, unhide=True):
        """
        Set card/tile image side from pair.

        Call item config method to update for current side of ImagePair.

        .. \_set_side([unhide:bool=True])

        *Parameters:*
          ``unhide``: {bool} True

        *Requires*:
          methods:
            ``._item.config``; ``._item.raise_item``.
          variables:
            ``._image``; ``.item``; ``._face``.
        """
        self._item.config(image=self._image[int(self._face)])
        self.raise_item(unhide)

    def update_back(self, back):
        """
        Update card/tile back image from pair.

        Set back image of ImagePair with given back. Update item image if face
        down.

        .. update_back(back:Image) -> None

        *Parameters:*
          ``back``: {Image}
            Image to set as back for ImagePair.

        *Requires*:
          methods:
            ``._item.config``.
          variables:
            ``._image``.
        """
        self._image = self._image._replace(back=back)
        if not self._face:
            self._item.config(image=back)

    def update(self, parent, coord, face, back):
        """
        Update card/tile image pair.

        Set ImagePair, then place or update item image for current side at given
        coord.

        .. update(parent:Canvas, coord:Point, face:Image, back:Image) -> None

        *Parameters:*
          ``parent``: {Canvas}
            Canvas widget.
          ``coord``: {Point(x:int, y:int)}
            Point to place on canvas.
          ``front``: {Image}
            Image to set as front for ImagePair.
          ``back``: {Image}
            Image to set as back for ImagePair.

        *Requires*:
          methods:
            ``canvas.ImageItem``.
          variables:
            ``._item.``._item.config``; ``._image``.
        """
        self._image = ImagePair(back=back, face=face)
        image = self._image[int(self.face_up)]
        if self._item is None:
            self._item = canvas.ImageItem(parent, coord, image=image)
        else:
            self._item.config(image=image)

Card = _OneImageCard